﻿using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

namespace Boggle.Net.Controls
{
    public abstract class ArrowLineBase : Shape
    {
        private readonly PathFigure _startPathFigure;
        private readonly PathFigure _endPathFigure;

        #region Dependency Properties

        /// <summary>
        ///     Identifies the ArrowAngle dependency property.
        /// </summary>
        public static readonly DependencyProperty ArrowAngleProperty =
            DependencyProperty.Register("ArrowAngle", typeof(double), typeof(ArrowLineBase),
                new FrameworkPropertyMetadata(50.0, FrameworkPropertyMetadataOptions.AffectsMeasure));

        /// <summary>
        ///     Gets or sets the angle between the two sides of the arrowhead.
        /// </summary>
        public double ArrowAngle
        {
            set { SetValue(ArrowAngleProperty, value); }
            get { return (double)GetValue(ArrowAngleProperty); }
        }

        /// <summary>
        ///     Identifies the ArrowLength dependency property.
        /// </summary>
        public static readonly DependencyProperty ArrowLengthProperty =
            DependencyProperty.Register("ArrowLength", typeof(double), typeof(ArrowLineBase),
                new FrameworkPropertyMetadata(6.0, FrameworkPropertyMetadataOptions.AffectsMeasure));

        /// <summary>
        ///     Gets or sets the length of the two sides of the arrowhead.
        /// </summary>
        public double ArrowLength
        {
            set { SetValue(ArrowLengthProperty, value); }
            get { return (double)GetValue(ArrowLengthProperty); }
        }

        /// <summary>
        ///     Identifies the ArrowEnds dependency property.
        /// </summary>
        public static readonly DependencyProperty ArrowEndsProperty =
            DependencyProperty.Register("ArrowEnds", typeof(ArrowEnds), typeof(ArrowLineBase),
                new FrameworkPropertyMetadata(ArrowEnds.End,FrameworkPropertyMetadataOptions.AffectsMeasure));

        /// <summary>
        ///     Gets or sets the property that determines which ends of the
        ///     line have arrows.
        /// </summary>
        public ArrowEnds ArrowEnds
        {
            set { SetValue(ArrowEndsProperty, value); }
            get { return (ArrowEnds)GetValue(ArrowEndsProperty); }
        }

        /// <summary>
        ///     Identifies the IsArrowClosed dependency property.
        /// </summary>
        public static readonly DependencyProperty IsArrowClosedProperty =
            DependencyProperty.Register("IsArrowClosed", typeof(bool), typeof(ArrowLineBase),
                new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsMeasure));

        /// <summary>
        ///     Gets or sets the property that determines if the arrow head
        ///     is closed to resemble a triangle.
        /// </summary>
        public bool IsArrowClosed
        {
            set { SetValue(IsArrowClosedProperty, value); }
            get { return (bool)GetValue(IsArrowClosedProperty); }
        }

        #endregion

        protected PathGeometry PathGeometry { get; set; }
        protected PathFigure PathFigure { get; set; }
        protected PolyLineSegment PolyLine { get; set; }

        protected ArrowLineBase()
        {
            PathGeometry = new PathGeometry();

            PolyLine = new PolyLineSegment();
            PathFigure = new PathFigure { Segments = { PolyLine } };

            _startPathFigure = new PathFigure { Segments = { new PolyLineSegment() } };
            _endPathFigure = new PathFigure { Segments = { new PolyLineSegment() } };
        }

        protected override Geometry DefiningGeometry
        {
            get
            {
                var count = PolyLine.Points.Count;

                if (count > 0)
                {
                    // Draw the arrow at the start of the line.
                    if ((ArrowEnds & ArrowEnds.Start) == ArrowEnds.Start)
                    {
                        var pt1 = PathFigure.StartPoint;
                        var pt2 = PolyLine.Points[0];
                        
                        PathGeometry.Figures.Add(CalculateArrow(_startPathFigure, pt2, pt1));
                    }

                    // Draw the arrow at the end of the line.
                    if ((ArrowEnds & ArrowEnds.End) == ArrowEnds.End)
                    {
                        var pt1 = count == 1 ? PathFigure.StartPoint : PolyLine.Points[count - 2];
                        var pt2 = PolyLine.Points[count - 1];

                        PathGeometry.Figures.Add(CalculateArrow(_endPathFigure, pt1, pt2));
                    }
                }
                else
                {
                    PathGeometry.Clear();
                }

                return PathGeometry;
            }
        }

        private PathFigure CalculateArrow(PathFigure pathFigure, Point pt1, Point pt2)
        {
            var vector = pt1 - pt2;
            vector.Normalize();
            vector *= ArrowLength;

            var segment = (PolyLineSegment)pathFigure.Segments[0];
            segment.Points.Clear();

            var m = new Matrix();
            m.Rotate(ArrowAngle / 2);

            pathFigure.StartPoint = pt2 + vector * m;
            segment.Points.Add(pt2);

            m.Rotate(-ArrowAngle);
            segment.Points.Add(pt2 + vector * m);

            pathFigure.IsClosed = IsArrowClosed;

            return pathFigure;
        }
    }
}
